package me.zd.tool.hardlink.controller.impl;

import me.zd.tool.hardlink.controller.IStartupController;
import me.zd.tool.hardlink.model.FullSetting;
import me.zd.tool.hardlink.service.ConfigService;
import me.zd.tool.hardlink.service.I18nService;
import me.zd.tool.hardlink.view.PairItem;
import me.zd.tool.hardlink.view.listener.CountDownButtonListener;
import me.zd.tool.hardlink.service.HardLinkService;
import me.zd.tool.hardlink.view.MainFrame;
import me.zd.tool.hardlink.view.ModelBinder;
import me.zd.tool.hardlink.view.PropertiesFileFilter;
import me.zd.tool.hardlink.view.listener.ModelLock;

import javax.swing.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.function.Consumer;


public class GUIController implements IStartupController {

    private final FullSetting setting = new FullSetting();
    private final ModelLock modelLock = new ModelLock();
    private final MainFrame mainFrame = new MainFrame();

    @Override
    public void startup(Map<String, String> argMap) {
        bindModel();
        bindAction();
        try {
            ConfigService.INS.importSetting(setting, null, true, System.out::println);
        } catch (IOException e) {
            mainFrame.appendThrowable(e);
        }
        try {
            updateViewFromModel();
        } catch (InterruptedException | InvocationTargetException e) {
            mainFrame.appendThrowable(e);
        }
        mainFrame.setVisible(true);
        mainFrame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                try {
                    saveSetting(null, System.out::println);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        });
    }

    private void loadSetting(String path, Consumer<String> monitor) throws IOException, InterruptedException, InvocationTargetException {
        modelLock.lock();
        try {
            boolean result = ConfigService.INS.importSetting(setting, path, false, monitor);
            if (result) {
                updateViewFromModel();
            }
        } finally {
            modelLock.unlock();
        }
    }

    private void saveSetting(String path, Consumer<String> monitor) throws IOException {
        modelLock.lock();
        try {
            ConfigService.INS.exportSetting(setting, path, monitor);
        } finally {
            modelLock.unlock();
        }
    }

    private void updateViewFromModel() throws InterruptedException, InvocationTargetException {
        SwingUtilities.invokeAndWait(() -> {
            mainFrame.tfFromPath.setText(setting.link.getFromPath());
            mainFrame.tfToPath.setText(setting.link.getToPath());
            mainFrame.tfFromPattern.setText(setting.link.getFromPattern());
            mainFrame.tfToPattern.setText(setting.link.getToPattern());
            mainFrame.tfIncludeSuffix.setText(setting.link.getIncludeSuffix());
            mainFrame.tfExcludeSuffix.setText(setting.link.getExcludeSuffix());
            mainFrame.spScanDepth.setValue(setting.link.getScanDepth());
            mainFrame.tfTargetFilePrefix.setText(setting.link.getTargetFilePrefix());
            mainFrame.spRegexpWithParentLevel.setValue(setting.link.getRegexpWithParentLevel());
            ComboBoxModel<PairItem> languageModel = mainFrame.cBoxSelectLanguage.getModel();
            int languageIndex = 0;
            for (int i = 0; i < languageModel.getSize(); i++) {
                PairItem item = languageModel.getElementAt(i);
                if (item.getValue().equals(setting.language.getLanguage())) {
                    languageIndex = i;
                    break;
                }
            }
            mainFrame.cBoxSelectLanguage.setSelectedIndex(languageIndex);
            mainFrame.cbUsingRegexp.setSelected(setting.link.isUsingRegexp());
            mainFrame.cbUsingRegexpReplacement.setSelected(setting.link.isUsingRegexpReplacement());
            mainFrame.cbUsingRegexpWithParentLevel.setSelected(setting.link.isUsingRegexpWithParentLevel());
            mainFrame.cbUsingModifiedTime.setSelected(setting.link.isUsingModifiedTime());
            mainFrame.tfBeginModifiedTime.setText(setting.link.getBeginModifiedTime());
            mainFrame.tfEndModifiedTime.setText(setting.link.getEndModifiedTime());
        });
        mainFrame.refreshAllText();
    }

    private void bindModel() {
        ModelBinder.bind(mainFrame.tfFromPath, setting.link::setFromPath, modelLock);
        ModelBinder.bind(mainFrame.tfToPath, setting.link::setToPath, modelLock);
        ModelBinder.bind(mainFrame.tfFromPattern, setting.link::setFromPattern, modelLock);
        ModelBinder.bind(mainFrame.tfToPattern, setting.link::setToPattern, modelLock);
        ModelBinder.bind(mainFrame.tfIncludeSuffix, setting.link::setIncludeSuffix, modelLock);
        ModelBinder.bind(mainFrame.tfExcludeSuffix, setting.link::setExcludeSuffix, modelLock);
        ModelBinder.bind(mainFrame.spScanDepth, number -> setting.link.setScanDepth(number.intValue()), modelLock);
        ModelBinder.bind(mainFrame.tfTargetFilePrefix, setting.link::setTargetFilePrefix, modelLock);
        ModelBinder.bind(mainFrame.spRegexpWithParentLevel, number -> setting.link.setRegexpWithParentLevel(number.intValue()), modelLock);
        ModelBinder.bind(mainFrame.cBoxSelectLanguage, item -> setting.language.setLanguage(item.getValue()), modelLock);
        ModelBinder.bind(mainFrame.cbUsingRegexp, setting.link::setUsingRegexp, modelLock);
        ModelBinder.bind(mainFrame.cbUsingRegexpReplacement, setting.link::setUsingRegexpReplacement, modelLock);
        ModelBinder.bind(mainFrame.cbUsingRegexpWithParentLevel, setting.link::setUsingRegexpWithParentLevel, modelLock);
        ModelBinder.bind(mainFrame.cbUsingModifiedTime, setting.link::setUsingModifiedTime, modelLock);
        ModelBinder.bind(mainFrame.tfBeginModifiedTime, setting.link::setBeginModifiedTime, modelLock);
        ModelBinder.bind(mainFrame.tfEndModifiedTime, setting.link::setEndModifiedTime, modelLock);
    }

    private void bindAction() {
        mainFrame.buttonStart.addActionListener(e -> {
            mainFrame.buttonStartI18n.updateI18n(1);
            mainFrame.buttonStart.setEnabled(false);
            new Thread(() -> {
                try {
                    mainFrame.cleanConsole();
                    mainFrame.appendConsole(I18nService.INS.get("processing.begin"));
                    HardLinkService.INS.createHardLink(setting.link, HardLinkService.TEST_MODE_OFF, mainFrame::appendConsole);
                } catch (Exception ex) {
                    mainFrame.appendThrowable(ex);
                } finally {
                    SwingUtilities.invokeLater(() -> {
                        mainFrame.buttonStartI18n.updateI18n(0);
                        mainFrame.buttonStart.setEnabled(true);
                    });
                }
            }).start();
        });
        mainFrame.cBoxSelectLanguage.addItemListener(e -> {
            if (e.getStateChange() == ItemEvent.SELECTED) {
                PairItem item = (PairItem) e.getItem();
                if (I18nService.INS.changeLanguage(item.getValue())) {
                    mainFrame.refreshAllText();
                }
            }
        });
        mainFrame.buttonTest.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (!((JButton) e.getSource()).isEnabled()) {
                    return;
                }
                mainFrame.buttonTestI18n.updateI18n(1);
                mainFrame.buttonTest.setEnabled(false);
                if (e.getButton() == MouseEvent.BUTTON1) {
                    new Thread(() -> {
                        String title = I18nService.INS.get("dialog.title.test.result");
                        try {
                            HardLinkService.INS.createHardLink(setting.link, HardLinkService.TEST_MODE_FIRST_ONLY,
                                    text -> mainFrame.popMessageDialog(title, text));
                        } catch (Exception ex) {
                            String exMsg = mainFrame.appendThrowable(ex);
                            mainFrame.popMessageDialog(title, exMsg);
                        } finally {
                            SwingUtilities.invokeLater(() -> {
                                mainFrame.buttonTestI18n.updateI18n(0);
                                mainFrame.buttonTest.setEnabled(true);
                            });
                        }
                    }).start();
                } else if (e.getButton() == MouseEvent.BUTTON3) {
                    new Thread(() -> {
                        String title = I18nService.INS.get("dialog.title.test.result");
                        try {
                            Map<File, File> linkedFileMap = HardLinkService.INS.createHardLink(setting.link,
                                    HardLinkService.TEST_MODE_ALL, text -> mainFrame.popMessageDialog(title, text));
                            if (!linkedFileMap.isEmpty()) {
                                linkedFileMap.forEach((file, destFile) -> {
                                    mainFrame.appendConsole(I18nService.INS.format("processing.test.linked",
                                            file.getAbsolutePath(), destFile.getAbsolutePath()));
                                });
                                mainFrame.appendConsole(I18nService.INS.format("processing.test.done", linkedFileMap.size()));
                                mainFrame.popMessageDialog(title, I18nService.INS.format("processing.test.done.pop",linkedFileMap.size()));
                            }
                        } catch (Exception ex) {
                            String exMsg = mainFrame.appendThrowable(ex);
                            mainFrame.popMessageDialog(title, exMsg);
                        } finally {
                            SwingUtilities.invokeLater(() -> {
                                mainFrame.buttonTestI18n.updateI18n(0);
                                mainFrame.buttonTest.setEnabled(true);
                            });
                        }
                    }).start();
                }
            }


        });
        mainFrame.buttonImport.addActionListener(e -> {
            File file = mainFrame.popFileChooserForFile(I18nService.INS.get("dialog.choose.import.file"), false,
                    null, PropertiesFileFilter.INS);
            if (file == null) {
                return;
            }
            mainFrame.buttonImport.setEnabled(false);
            new Thread(() -> {
                try {
                    loadSetting(file.getAbsolutePath(), mainFrame::appendConsole);
                } catch (IOException | InterruptedException | InvocationTargetException ex) {
                    mainFrame.appendThrowable(ex);
                } finally {
                    SwingUtilities.invokeLater(() -> mainFrame.buttonImport.setEnabled(true));
                }
            }).start();
        });
        mainFrame.buttonExport.addActionListener(e -> {
            File file = mainFrame.popFileChooserForFile(I18nService.INS.get("dialog.choose.export.file"), true,
                    null, PropertiesFileFilter.INS);
            if (file == null) {
                return;
            }
            mainFrame.buttonExport.setEnabled(false);
            new Thread(() -> {
                String filePath = file.getAbsolutePath();
                if (!filePath.contains(".")) {
                    filePath += ".properties";
                }
                try {
                    saveSetting(filePath, mainFrame::appendConsole);
                } catch (IOException ex) {
                    mainFrame.appendThrowable(ex);
                } finally {
                    SwingUtilities.invokeLater(() -> mainFrame.buttonExport.setEnabled(true));
                }
            }).start();
        });
        CountDownButtonListener resetButtonListener = new CountDownButtonListener(10,
                mainFrame.labelResetI18n::updateI18n,
                () -> {
                    new Thread(() -> {
                        try {
                            ConfigService.INS.resetToDefault(setting);
                            updateViewFromModel();
                        } catch (InterruptedException | InvocationTargetException e) {
                            mainFrame.appendThrowable(e);
                        }
                    }).start();
                    mainFrame.labelResetI18n.updateI18n(0);
                    mainFrame.popMessageDialog(I18nService.INS.get("dialog.setting.reset"),
                            I18nService.INS.get("reset.setting.do"));
                });
        mainFrame.buttonReset.addActionListener(resetButtonListener);

        mainFrame.buttonAutoFillPattern.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (!((JButton) e.getSource()).isEnabled()) {
                    return;
                }
                if (e.getButton() == MouseEvent.BUTTON1) {
                    fill(true);
                } else if (e.getButton() == MouseEvent.BUTTON3) {
                    fill(false);
                }
            }

            private void fill(boolean chooseFile) {
                File inputFile = null;
                if (chooseFile) {
                    inputFile = mainFrame.popFileChooserForFile(I18nService.INS.get("dialog.choose.fill.file.name"), false,
                            mainFrame.tfFromPath.getText(), null);
                } else {
                    try {
                        inputFile = HardLinkService.INS.findFirstIgnoreRegexp(setting.link, text -> {
                        });
                    } catch (IOException ex) {
                        mainFrame.appendThrowable(ex);
                    }
                }
                if (inputFile == null) {
                    return;
                }
                String fillPattern;
                if (setting.link.isUsingRegexp() && setting.link.isUsingRegexpWithParentLevel()) {
                    int parentLevel = setting.link.getRegexpWithParentLevel();
                    File checkFile = inputFile;
                    StringBuilder sb = new StringBuilder(checkFile.getName());
                    while (parentLevel-- > 0 && checkFile.getParentFile() != null) {
                        checkFile = checkFile.getParentFile();
                        sb.insert(0, File.separator).insert(0, checkFile.getName());
                    }
                    fillPattern = sb.toString();
                } else {
                    fillPattern = inputFile.getName();
                }
                mainFrame.tfFromPattern.setText(fillPattern.replaceAll("([$\\[\\]{}+?^()|.\\\\])", "\\\\$1"));
            }
        });
    }
}
